package example.demo.route.plugins;


import example.demo.route.aop.RouteContext;
import example.demo.route.handler.SqlHandlerInterface;
import org.apache.ibatis.executor.statement.PreparedStatementHandler;
import org.apache.ibatis.executor.statement.RoutingStatementHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.logging.Log;
import org.apache.ibatis.logging.jdbc.ConnectionLogger;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.datasource.DataSourceUtils;
import org.springframework.stereotype.Component;
import org.springframework.util.ReflectionUtils;

import javax.sql.DataSource;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;
import java.util.Properties;

/**
 * 这个自动就会加入到mybatis里面去，无需配置
 * <p>
 * 第一阶段【会场创建jdbc的statement对象】 第一阶段无需改动sql
 * mybatis的statement先是prepare
 * 第二阶段【执行sql语句】                第二阶段就需要改动sql了
 * 之后要么是query【select】或者是【update，insert，delete】
 */
//@Component
@Deprecated
@Intercepts({
        //Statement prepare(Connection connection, Integer transactionTimeout)  再创建Statement之前做。原生jdbc的
        @Signature(type = StatementHandler.class, method = "prepare",
                args = {Connection.class, Integer.class}),
        //<E> List<E> query(Statement statement, ResultHandler resultHandler)   
        @Signature(type = StatementHandler.class, method = "query",
                args = {Statement.class, ResultHandler.class}),
        @Signature(type = StatementHandler.class, method = "update",
                args = {Statement.class})})
public class RoutePlugin implements Interceptor {

    private List<SqlHandlerInterface> sqlHandlers;

    @Autowired
    DataSource dataSource;

    @Autowired
    public void setSqlHandlers(List<SqlHandlerInterface> sqlHandlers) {
        this.sqlHandlers = sqlHandlers;
    }

    /**
     * 装饰器模式
     * RoutingStatementHandler会持有【PreparedStatementHandler，CallableStatementHandler，SimpleStatementHandler】
     * RoutingStatementHandler的 delegate 就是上面的其中三个其中的一个
     * 也就是RoutingStatementHandler委托给delegate去做事情。
     * <p>
     * BaseStatementHandler的三个子类【PreparedStatementHandler，CallableStatementHandler，SimpleStatementHandler】
     * 那三个子类都没有自己的属性，都是继承自BaseStatementHandler的
     */

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        if (invocation.getMethod().getName().equals("prepare")) {
            return invocation.proceed();
        }
        Object target = invocation.getTarget();
        if (target instanceof RoutingStatementHandler) {
            Integer[] tables = RouteContext.tables_thread_local.get();
            Integer[] tablesIdxs = RouteContext.tableIdxs_thread_local.get();
            /**<? 避免内存遗漏*/
            Integer[] integer = tables == null ? tablesIdxs : tables;
            if (null != integer && integer.length != 0) {
                RouteContext.removeAll();
                /*委托者*/
                RoutingStatementHandler routingStatementHandler = (RoutingStatementHandler) target;
                Class<RoutingStatementHandler> routingStatementHandlerClass = RoutingStatementHandler.class;

                /*真正的操作人*/
                Field delegate = routingStatementHandlerClass.getDeclaredField("delegate");
                delegate.setAccessible(true);
                StatementHandler statementHandler = (StatementHandler) delegate.get(routingStatementHandler);

                BoundSql boundSql = statementHandler.getBoundSql();
                /**<? 获取到sql语句*/
                String sql = boundSql.getSql();
                /**<? 拿到sql属性*/
                Field sqlField = BoundSql.class.getDeclaredField("sql");
                sqlField.setAccessible(true);

                return handlerDisparth(integer, sql, sqlField, invocation, boundSql, statementHandler);
            }
        }
        /*如果是prepare，则内部是是创建statement对象，update，query的话则内部是执行sql语句*/
        return invocation.proceed();
    }

    private Object handlerDisparth(Integer[] integers, String sql, Field sqlField, Invocation invocation, BoundSql boundSql, StatementHandler statementHandler) {
        if (sql.contains("SELECT") || sql.contains("select")) {
            return handlerSelect(integers, sql, sqlField, invocation, boundSql, statementHandler);
        } else if (sql.contains("INSERT") || sql.contains("insert")) {
            return handlerInsert(integers, sql, sqlField, invocation, boundSql, statementHandler);
        } else if (sql.contains("UPDATE") || sql.contains("update")) {
            return handlerUpdate(integers, sql, sqlField, invocation, boundSql, statementHandler);
        } else {//delete
            return handlerDelete(integers, sql, sqlField, invocation, boundSql, statementHandler);
        }
    }

    private Object handlerSelect(Integer[] integers, String sql, Field sqlField, Invocation invocation, BoundSql boundSql, StatementHandler statementHandler) {
        for (Integer integer : integers) {
            int intValue = integer.intValue();
            if (intValue != 0) {
                String tbNewName;
                String tbName = getTableNameBySelect(sql);
                tbNewName = " " + tbName.trim() + intValue + " ";
                String replaceSql = sql.replace(tbName, tbNewName);
                try {
                    sqlField.set(boundSql, replaceSql);
                    Connection connection = DataSourceUtils.getConnection(dataSource);
                    Connection connectionProxy = ConnectionLogger.newInstance(connection, getStatementLog(statementHandler), 1);
                    Statement statement = statementHandler.prepare(connectionProxy, null);
                    statementHandler.parameterize(statement);
                    invocation.getArgs()[0] = statement;
                    List<Object> list = (List<Object>) invocation.proceed();
                    if (list.size() > 0) {
                        return list;
                    }
                } catch (IllegalAccessException | InvocationTargetException | SQLException e) {
                    e.printStackTrace();
                }
            } else {
                try {
                    /**<? mybatis没查到也是返回一个不为null的list*/
                    List<Object> list = (List<Object>) invocation.proceed();
                    if (list.size() != 0) {
                        return list;
                    }
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }

            }
        }
        return null;
    }

    private Log getStatementLog(StatementHandler statementHandler) {
        try {
            PreparedStatementHandler prepareStatement = (PreparedStatementHandler) statementHandler;
            Field mappedStatement = getField(prepareStatement, "mappedStatement");
            MappedStatement mappedStatementO = (MappedStatement) mappedStatement.get(prepareStatement);
            return mappedStatementO.getStatementLog();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return null;
    }

//    private Statement getStatement(StatementHandler statementHandler, Field sqlF, String tableName, BoundSql bsinstance, String sql) {
//        try {
//            sqlF.set(bsinstance, repalceTableName(sql, tableName));
//            Connection connection = DataSourceUtils.getConnection(dataSource);
//            Connection connectionProxy = ConnectionLogger.newInstance(connection, getStatementLog(statementHandler), 1);
//            Statement statement = statementHandler.prepare(connectionProxy, null);
//            statementHandler.parameterize(statement);
//            return statement;
//        } catch (SQLException throwables) {
//            throwables.printStackTrace();
//        } catch (IllegalAccessException e) {
//            e.printStackTrace();
//        }
//        return null;
//    }

//    private String repalceTableName(String sql, String newTable) {
//        String from = sql.substring(0, sql.indexOf("from") + 4);
//        String where = sql.substring(sql.indexOf("where"));
//        return from + " " + newTable + " " + where;
//    }

    private Field getField(Object o, String name) {
        Field field = ReflectionUtils.findField(o.getClass(), name);
        ReflectionUtils.makeAccessible(field);
        return field;
    }

    //SELECT id,username,real_name,sex,mobile,email,note,position_id FROM t_user WHERE id=?
    private String getTableNameBySelect(String sql) {
        return sql.substring(sql.lastIndexOf("FROM") + 4, sql.lastIndexOf("WHERE"));
    }

//    public static void main(String[] args) {
//        String sql = "DELETE FROM t_user WHERE id=?";
//        System.out.println(sql.substring(sql.indexOf("FROM") + 4, sql.indexOf("WHERE")));
//    }

    //INSERT INTO t_user ( email ) VALUES ( ? )
    private String getTableNameByInsert(String sql) {
        return sql.substring(sql.indexOf("INTO") + 4, sql.indexOf("("));
    }

    //UPDATE t_user SET email=? WHERE id=?
    private String getTableNameByUpdate(String sql) {
//        return sql.substring(sql.indexOf("UPDATE") + 6, sql.indexOf("SET"));
        return sql.substring(6, sql.indexOf("SET"));
    }

    //DELETE FROM t_user WHERE id=?
    private String getTableNameByDelete(String sql) {
        return sql.substring(sql.indexOf("FROM") + 4, sql.indexOf("WHERE"));
    }

    private Object handlerInsert(Integer[] integers, String sql, Field sqlField, Invocation invocation, BoundSql boundSql, StatementHandler statementHandler) {
        String tbName = getTableNameByInsert(sql);
        return null;
    }

    private Object handlerUpdate(Integer[] integers, String sql, Field sqlField, Invocation invocation, BoundSql boundSql, StatementHandler statementHandler) {
        String tbName = getTableNameByUpdate(sql);
        return null;
    }

    /**
     * 大多数都是假删除，即有一个逻辑删除字段
     */
    private Object handlerDelete(Integer[] integers, String sql, Field sqlField, Invocation invocation, BoundSql boundSql, StatementHandler statementHandler) {
        String tbName = getTableNameByDelete(sql);
        return null;
    }


    @Override
    public Object plugin(Object target) {
        System.out.println();
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {

    }
}
